
package com.x52im.rbchat.logic.chat_root;

import android.content.Context;
import android.graphics.Bitmap;
import android.text.SpannableString;
import android.text.style.DynamicDrawableSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.eva.android.BitmapHelper;
import com.eva.android.widget.AsyncBitmapLoader;
//import com.eva.android.widget.AsyncBitmapLoader2;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.x.RoundedBitmapDrawableUtility;
import com.eva.epc.common.util.CommonUtils;
import com.x52im.rainbowchat.im.dto.QuoteMeta;
import com.x52im.rbchat.R;
import com.x52im.rbchat.cache.ImageCacheLoader;
import com.x52im.rainbowchat.http.logic.dto.TimeToolKit;
import com.x52im.rainbowchat.im.dto.MsgType;
import com.x52im.rbchat.logic.chat_friend.gift.GiftsProvider;
import com.x52im.rbchat.logic.chat_friend.gift.model.Gift;
import com.x52im.rbchat.logic.chat_friend.gift.model.GiftsMeta;
import com.x52im.rbchat.logic.chat_root.face.EmojiUtil;
import com.x52im.rbchat.logic.chat_root.impl.Quote4InputWrapper;
import com.x52im.rbchat.logic.chat_root.model.ContactMeta;
import com.x52im.rbchat.logic.chat_root.model.FileMeta;
import com.x52im.rbchat.logic.chat_root.model.LocationMeta;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.model.MessageExt;
import com.x52im.rbchat.logic.chat_root.model.VoipRecordMeta;
import com.x52im.rbchat.logic.chat_root.sendfile.SendFileHelper;
import com.x52im.rbchat.logic.chat_root.sendimg.SendImageHelper;
import com.x52im.rbchat.logic.chat_root.sendlocation.utils.LocationUtils;
import com.x52im.rbchat.logic.chat_root.sendshortvideo.ReceivedShortVideoHelper;
import com.x52im.rbchat.logic.chat_root.sendvoice.SendVoiceHelper;

import java.text.MessageFormat;
import java.util.Observable;
import java.util.Observer;

/**
 * 本类仅作为 AbstractChattingListAdapter 的辅助类使用。
 *
 * 作用是将一些可以剥离出来的代码放到本类中，从而降低AbstractChattingListAdapter的代码行数，提升
 * AbstractChattingListAdapter的可维护性和代码可读性，别无它用！本类中的代码，可以无条件合并到
 * AbstractChattingListAdapter中（如果你不介意代码量大，看起来很吓的人话。。。）。
 *
 * @author Jack Jiang
 * @since 6.0
 * @see AbstractChattingListAdapter
 */
public class AbstractChattingListAdapterExt
{
    private final static String TAG = AbstractChattingListAdapterExt.class.getSimpleName();

    //------------------------------------------------------------------------------- utilities
    /**
     * 根据消息类型inflate对应的view对象.
     *
     * @param layoutInflater
     * @param msgType
     * @return
     */
    static View inflateChattingListItemView(LayoutInflater layoutInflater, int msgType, boolean isOutgoing)
    {
        View convertView = null;

        try
        {
            switch(msgType)
            {
                case MsgType.TYPE_TEXT:
                    convertView = layoutInflater.inflate(isOutgoing? R.layout.chatting_list_item_right_text : R.layout.chatting_list_item_left_text, null);
                    break;
                case MsgType.TYPE_IMAGE:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_image:R.layout.chatting_list_item_left_image, null);
                    break;
                case MsgType.TYPE_VOICE:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_voice:R.layout.chatting_list_item_left_voice, null);
                    break;
                case MsgType.TYPE_GIFT$SEND:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_gift4send:R.layout.chatting_list_item_left_gift4send, null);
                    break;
                case MsgType.TYPE_GIFT$GET:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_gift4get:R.layout.chatting_list_item_left_gift4get, null);
                    break;
                case MsgType.TYPE_FILE:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_file:R.layout.chatting_list_item_left_file, null);
                    break;
                case MsgType.TYPE_SYSTEAM$INFO:
                    convertView = layoutInflater.inflate(R.layout.chatting_list_item_systeminfo, null);
                    break;
                case MsgType.TYPE_SHORTVIDEO:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_shortvideo:R.layout.chatting_list_item_left_shortvideo, null);
                    break;
                case MsgType.TYPE_CONTACT:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_contact:R.layout.chatting_list_item_left_contact, null);
                    break;
                case MsgType.TYPE_LOCATION:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_location:R.layout.chatting_list_item_left_location, null);
                    break;
                case MsgType.TYPE_VOIP$RECORD:
                    convertView = layoutInflater.inflate(isOutgoing?R.layout.chatting_list_item_right_voip:R.layout.chatting_list_item_left_voip, null);
                    break;
                case MsgType.TYPE_REVOKE:
                    convertView = layoutInflater.inflate(R.layout.chatting_list_item_revoked, null);
                    break;
            }
        }
        catch(Exception e)
        {
            Log.w(TAG, e);
        }

        return convertView;
    }

    /**
     * 针对发出的图片或语音留言消息的getView方法补充处理.
     * <p>
     * <b>图片或语音留言的发送（包括上传图片或语音留言到服务端、发送图片或语音留言消息到好友处）
     * 异步处理机制的说明（仿微信等）：</b><br>
     * 原理是先把要发送的图片或语音留言消息显示在本地的聊天列表中，列表的ListAdapter在getView方法
     * 中刷新ui显示的同时来异步完成图片或语音留言消息的发送（那些处于“pending”状态的消息即是此种类型），
     * 否则图片或语音留言消息的发送得在要发送的图片或语音留言预览界面中实现图片或语音留言发送（用户势必要在此Activity中完成
     * 图片或语音留言消息的：图片或语音留言数据上传、图片或语音留言消息发送等可能的耗时操作），这将使得用户等待直到该条图片或语音留言
     * 消息发送完成。根主流IM（包括微信、QQ等）相比，这样的实现方法因用户需在图片或语音留言发送界面中等
     * 待而大大降低用户体验（给用户的感觉是需要等待，网络不好的情况下可能会出现长时间等待，因
     * 为重传机制的存在），而异步实现虽然技术上有点别扭，但能提升用户体验（理论上用户在发送图
     * 片消息时不用等待前一条发送完成即可进入下一条的发送，会让用户感觉发送操作很流畅）。
     *
     * @param context
     * @param entity 聊天界面中对应于本条消息的DTO对象句柄
     * @param tvSendstatusSencondaryLayout 用于图片或语音留言上传（到服务端暂存的进度总布局）
     * @param tvSendstatusSencondaryHintView 用于图片或语音留言上传（到服务端暂存）时的进度提示文本组件
     * @param observerForImageAndVoiceUploadProcessOK 图片或语音留言上传处理成功后要通知的观察者，本参数为null即表示无需知
     * @see #setVisibleForImageAndVoiceMessageSecondaryProgress(View, TextView, boolean)
     * @see #setVisibleForImageMessageSecondaryProgress(View, TextView, boolean, String)
     * @see Message
     */
    static void processImageOrVoiceMessageOfSentForGetView(final Context context
            , final Message entity, final View tvSendstatusSencondaryLayout
            , final TextView tvSendstatusSencondaryHintView
            , final Observer observerForImageAndVoiceUploadProcessOK, int msgType)
    {
        switch(entity.getSendStatusSecondary())
        {
            case Message.SendStatusSecondary.processOk:
            case Message.SendStatusSecondary.none:
            {
                // 刷新进度提示相关ui
                setVisibleForImageAndVoiceMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView, false);
                break;
            }
            // 如果是“等待处理“状态下的消息则意味着接下来需要：先上传图片到服务端、再发送图片消息给好友
            case Message.SendStatusSecondary.pending:
            {
                setVisibleForImageMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView, true, context.getString(R.string.chat_sendpic_send_status_sencondary_hint_pending));
                if(entity.getText() != null)
                {
                    // 图片消息时，此参数中存放的是要发送的图片文件名
                    final String imageOrVoiceFileName = entity.getText();
                    // 图片消息时，此参数中存放的是用于QoS机制的消息指纹
                    final String fingerPring = entity.getFingerPrintOfProtocal();

                    // 处理结果观察者实现对象
                    Message.SendStatusSecondaryResult sendStatusSecondaryResult = new Message.SendStatusSecondaryResult(){
                        @Override
                        public void processing()
                        {
                            // 设置“处理中”状态
                            entity.setSendStatusSecondary(Message.SendStatusSecondary.processing);
                            // 并更新进度提示相关ui
                            setVisibleForImageMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView
                                    , true, context.getString(R.string.chat_sendpic_send_status_sencondary_hint_sending));
                        }
                        @Override
                        public void processFaild()
                        {
                            // 设置文件的处理状态为“处理失败”状态
                            entity.setSendStatusSecondary(Message.SendStatusSecondary.processFaild);
                            // 并直接像微信一样标识整个消息的发送状态为失败（add by JackJiang 20180913）
                            entity.setSendStatus(Message.SendStatus.sendFaild);
                        }
                        @Override
                        public void processOk()
                        {
                            // 设置“处理成功(完成)”状态
                            entity.setSendStatusSecondary(Message.SendStatusSecondary.processOk);
                            // 并更新进度提示相关ui
                            setVisibleForImageAndVoiceMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView, false);

                            // 同时通知观察者哦
                            if(observerForImageAndVoiceUploadProcessOK != null)
                                // 注意：目前传给观察者的参数是一个2元Object数据哦
                                observerForImageAndVoiceUploadProcessOK.update(null, new Object[]{imageOrVoiceFileName, fingerPring});
                        }
                    };

                    if(msgType == MsgType.TYPE_IMAGE)
                        // 处理图片的上传和消息的发送
                        SendImageHelper.processImageUpload(context, imageOrVoiceFileName, sendStatusSecondaryResult, false);
                    else if(msgType == MsgType.TYPE_VOICE)
                        // 处理语音留言的上传和消息的发送
                        SendVoiceHelper.processVoiceUpload(context, imageOrVoiceFileName, sendStatusSecondaryResult, false);
                }

                break;
            }
            case Message.SendStatusSecondary.processing:
            {
                // 刷新进度提示相关ui
                setVisibleForImageMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView, true, context.getString(R.string.chat_sendpic_send_status_sencondary_hint_sending));
                break;
            }
            case Message.SendStatusSecondary.processFaild:
            {
                // 刷新进度提示相关ui
                setVisibleForImageMessageSecondaryProgress(tvSendstatusSencondaryLayout, tvSendstatusSencondaryHintView, true, context.getString(R.string.chat_sendpic_send_status_sencondary_hint_faild));
                break;
            }
        }
    }

    /**
     * 针对发出的大文件消息的getView方法补充处理.
     *
     * @param entity
     * @param viewHolder
     */
    static void processBigFileMessageOfSentForGetView(final Message entity, final ChattingListViewHolder viewHolder)
    {
        switch(entity.getSendStatusSecondary())
        {
            case Message.SendStatusSecondary.processOk:
                viewHolder.evProgressBar.setProgress(entity.getSendStatusSecondaryProgress());
            case Message.SendStatusSecondary.none:
            {
                // 关闭上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.GONE);
                //# Bug FIX： 20180426 by JackJiang START
                //# 因为ListView的组件复用机制，当前一个view的实体对应的进度是100时，新的上传文件会在进度第1次置为processing
                //# 状态时刷新UI时，会错误地显示前一个ListItem的entity的progress值（即100），进而给用户一个错觉：明明刚开始
                //# 上传，却把初始的进度显示为100%。重置此值可解决此问题：实际上重置从逻辑上也是合理的，必竟都不需要显示进度条了，
                //# progrss值重置为初始值也没什么不好，反正此entity的主状态已经处于一个完结的状态中（processOk或none）
                // 在隐藏进度条的显示同时，重置进度为0。
                entity.setSendStatusSecondaryProgress(0);// !!!!!!!!!!!!!!!!!!!!!!!!!
                //# Bug FIX： 20180426 by JackJiang END
                break;
            }
            // 如果是“等待处理“状态下的消息则意味着接下来需要：先上传图片到服务端、再发送图片消息给好友
            case Message.SendStatusSecondary.pending:
            {
                // 显示上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.VISIBLE);
                break;
            }
            case Message.SendStatusSecondary.processing:
            {
//				Log.e("AAA-P", "[processing]progress="+entity.getSendStatusSecondaryProgress());

                // 显示上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.VISIBLE);
                viewHolder.evProgressBar.setProgress(entity.getSendStatusSecondaryProgress());
                break;
            }
            case Message.SendStatusSecondary.processFaild:
            {
                // 关闭上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.GONE);
                break;
            }
        }
    }

    /**
     * 针对发出的短视频消息的getView方法补充处理.
     *
     * @param entity
     * @param viewHolder
     */
    static void processShortVideoMessageOfSentForGetView(final Message entity, final ChattingListViewHolder viewHolder)
    {
        switch(entity.getSendStatusSecondary())
        {
            case Message.SendStatusSecondary.processOk:
            case Message.SendStatusSecondary.none:
            {
                // 关闭上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.GONE);
//                entity.setSendStatusSecondaryProgress(0);
                viewHolder.evSendstatusSencondaryHintView.setText("");
                break;
            }
            // 如果是“等待处理“状态下的消息则意味着接下来需要：先上传图片到服务端、再发送图片消息给好友
            case Message.SendStatusSecondary.pending:
            {
                // 显示上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.VISIBLE);
                viewHolder.evSendstatusSencondaryHintView.setText("%0");
                break;
            }
            case Message.SendStatusSecondary.processing:
            {
//                Log.e("AAA-P", "[processing]progress="+entity.getSendStatusSecondaryProgress());

                // 显示上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.VISIBLE);
//                viewHolder.tvProgressBar.setProgress(entity.getSendStatusSecondaryProgress());
                viewHolder.evSendstatusSencondaryHintView.setText("%"+entity.getSendStatusSecondaryProgress());
                break;
            }
            case Message.SendStatusSecondary.processFaild:
            {
                // 关闭上传进度提示ui
                viewHolder.evSendstatusSencondaryLayout.setVisibility(View.GONE);
                break;
            }
        }
    }

    /**
     * 设置图片或语音留言消息发送时各种UI的展现.
     *
     * @param tvSendstatusSencondaryLayout 用于图片或语音留言上传（到服务端暂存的进度总布局）
     * @param tvSendstatusSencondaryHintView  用于图片或语音留言上传（到服务端暂存）时的进度提示文本组件
     * @param visible
     * @see #setVisibleForImageMessageSecondaryProgress(View, TextView, boolean, String)
     */
    static void setVisibleForImageAndVoiceMessageSecondaryProgress(
            final View tvSendstatusSencondaryLayout, final TextView tvSendstatusSencondaryHintView, boolean visible)
    {
        setVisibleForImageMessageSecondaryProgress(tvSendstatusSencondaryLayout
                , tvSendstatusSencondaryHintView, visible, null);
    }
    static void setVisibleForImageMessageSecondaryProgress(final View tvSendstatusSencondaryLayout
            , final TextView tvSendstatusSencondaryHintView, boolean visible, String text)
    {
        if(tvSendstatusSencondaryLayout != null)
        {
            if(visible)
                tvSendstatusSencondaryLayout.setVisibility(View.VISIBLE);
            else
                tvSendstatusSencondaryLayout.setVisibility(View.GONE);
        }

        // 设置进度提示文本
        if(tvSendstatusSencondaryHintView != null && text != null)
            tvSendstatusSencondaryHintView.setText(text);
    }

    /**
     * 为聊天列表item的各view设置显示内容.
     *
     * @param context
     * @param listAdapter
     * @param viewHolder
     * @param msgContent
     * @param msgType
     * @param quoteMeta 消息引用信息（当前仅用于文本消息时），此字段可为空（表示本条无引用消息）
     */
    static void setChatMessageItemContentValue(
            final Context context
            , final BaseAdapter listAdapter
            , final ChattingListViewHolder viewHolder
            , int chatType
            , String toId
            , String msgContent
            , final int msgType
            , AsyncBitmapLoader asyncLoader
            , boolean isOutgoing
            , QuoteMeta quoteMeta
    )
    {
        switch(msgType)
        {
            case MsgType.TYPE_IMAGE:
            {
                String imageFileName = msgContent;
                if (imageFileName != null) {
                    String httpURL = SendImageHelper.getImageDownloadURL(context, "th_" + imageFileName, false);
                    String fname = (isOutgoing ? imageFileName : "th_" + imageFileName );// 收到的图片，只需要显示缩略图即可，缩略图在服务端的格式是：“th_文件名”

//                    // 发出的图片消息
//                    if (isOutgoing) {
//                        loadImageForMessage(context, listAdapter, viewHolder.mvContentIcon
//                                // 本地发出的图片当然不需要去网络取了（也取不到哦），加载url传null即可
//                                , httpURL // @since 10.0：本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
//                                // 本地发出的图片就没有缩略图了，直接用之前发出图片消息时的文件名即可
//                                , imageFileName
//                                , asyncLoader);
//                    }
//                    // 收到的图片消息
//                    else {
//                        loadImageForMessage(context, listAdapter, (ImageView) viewHolder.mvContentIcon
//                                // 本地没有缓存的话尝试从网络读取（图片的缩略图），读取缩略图时无需转储
//                                , httpURL
//                                , "th_" + imageFileName // 收到的图片，只需要显示缩略图即可，缩略图在服务端的格式是：“th_文件名”
//                                , asyncLoader);
//
//                    }

                    loadImageForMessage(context, listAdapter, viewHolder.mvContentIcon
                            // 本地发出的图片当然不需要去网络取了（也取不到哦），加载url传null即可
                            , httpURL // @since 10.0：本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
                            // 本地发出的图片就没有缩略图了，直接用之前发出图片消息时的文件名即可
                            , fname
                            , asyncLoader);
                }
                break;
            }
            // 语音留言消息
            case MsgType.TYPE_VOICE:
            {
                String voiceFileName = (String)msgContent;
                if(voiceFileName != null)
                {
                    // 从文件名中解析出语音时长（单位：秒）
                    int duration = SendVoiceHelper.getDurationFromVoiceFileName(voiceFileName);
                    // 显示语音时长
                    viewHolder.mvContent.setText(String.valueOf(duration)+"''");
                }
                break;
            }
            // “赠送的礼品”消息
            case MsgType.TYPE_GIFT$SEND:
            // “索取礼品”消息
            case MsgType.TYPE_GIFT$GET:
            {
                // ** 读取礼品的图标并现示到UI上
                final String giftIdent = (String)msgContent; // 此内容就是gift的ident字符串

                GiftsMeta _gm = GiftsProvider.getIntance().getGiftsMeta(false);
                // 如果数据已经加载完成就直接到ui上显示吧
                if(_gm.isHasLoaded())
                {
                    if(_gm.getGift(giftIdent) != null)
                    {
                        //------------------------------------------------------- 【1】与【2】处代码完全一致！
                        //** 礼品数据成功加载完成后把图标刷新到UI上
                        GiftsMeta.showGiftDrawable(context, (ImageView)viewHolder.mvContentIcon, _gm.getGift(giftIdent).getRes_drawable_id());

                        //** 设置“台词”哦（目前只针对“索取”消息）
                        if(viewHolder.mvContentDesc != null)
                        {
                            // 只针对“索取”的消息哦
                            if(msgType == MsgType.TYPE_GIFT$GET)
                                viewHolder.mvContentDesc.setText(_gm.getGift(giftIdent).getRes_dialogue());
                        }

                        //** 设置“价格”哦（目前只针对“赠送”消息）
                        if(viewHolder.mvContentDesc != null)
                        {
                            // 只针对“索取”的消息哦
                            if(msgType == MsgType.TYPE_GIFT$SEND)
                                viewHolder.mvContentDesc.setText(String.valueOf(_gm.getGift(giftIdent).getPrice()));
                        }
                    }
                }
                else
                {
                    // 注意：该礼品元数据很可能还没有从服务端加载，所以以下代码需要从异步线
                    //       程中执行，并在没有加载的情况下首先加载礼品数据
                    new GiftsProvider.LoadGiftsMetaAsyncTask(context).setShowProgress(false).execute(_gm
                            , new Observer(){
                                @Override
                                public void update(Observable observable, Object data)
                                {
                                    GiftsMeta gm = (GiftsMeta)data;
                                    Gift g = gm.getGift(giftIdent);

                                    if(g != null)
                                    {
                                        //------------------------------------------------------- 【2】与【1】处代码完全一致！
                                        //** 礼品数据成功加载完成后把图标刷新到UI上
                                        GiftsMeta.showGiftDrawable(context, (ImageView)viewHolder.mvContentIcon, g.getRes_drawable_id());

                                        //** 如果是“索取”消息，则还要设置“台词”哦
                                        if(viewHolder.mvContentDesc != null)
                                        {
                                            // 只针对“索取”的消息哦
                                            if(msgType == MsgType.TYPE_GIFT$GET)
                                                viewHolder.mvContentDesc.setText(g.getRes_dialogue());
                                        }
                                    }
                                    else
                                        Log.w(TAG, "gm.getGift(giftIdent) is null!!!!");
                                }
                            }
                    );
                }

                break;
            }
            // 文件消息
            case MsgType.TYPE_FILE:
            {
                FileMeta fileMeta = FileMeta.fromJSON(msgContent);
                if(fileMeta != null)
                {
                    String fileName = fileMeta.getFileName();
                    String fileMd5 = fileMeta.getFileMd5();
                    long fileLength = fileMeta.getFileLength();

                    // 显示文件信息
                    ((TextView)viewHolder.mvContent).setText(fileName);
                    viewHolder.mvContentDesc.setText(CommonUtils.getConvenientFileSize(fileLength, 2));//""+fileLength+","+fileMd5);
                    viewHolder.mvContentIcon.setImageDrawable(context.getResources().getDrawable(SendFileHelper.getFileIconDrawableId(fileName)));
                }
                break;
            }
            // 短视频消息
            case MsgType.TYPE_SHORTVIDEO:
            {
                FileMeta fileMeta = FileMeta.fromJSON(msgContent);
                if(fileMeta != null)
                {
                    String fileName = fileMeta.getFileName();
                    String fileMd5 = fileMeta.getFileMd5();
                    long fileLength = fileMeta.getFileLength();

                    // 显示文件信息
                    if(fileName != null)
                    {
                        // 从文件名中解析出语音时长（单位：秒）
                        int duration = SendVoiceHelper.getDurationFromVoiceFileName(fileName);
                        String durationHuman = TimeToolKit.getMMSSFromSS(duration);
                        // 显示语音时长
                        ((TextView)viewHolder.mvContent).setText(durationHuman);//String.valueOf(duration)+"''");
                    }

                    // 视频首帧预览图的文件名（本地保存的名）
                    final String imgLocalSavedName = ReceivedShortVideoHelper.constructShortVideoThumbName_localSaved(fileName);
                    String httpURL = ReceivedShortVideoHelper.getShortVideoThumbDownloadURL(context, imgLocalSavedName, fileMd5);

//                    if(isOutgoing) {
//                        // 发出的消息
//                        loadThumbImageForShortVideo(context
//                                , listAdapter
//                                , (ImageView) viewHolder.mvContentIcon
//                                // 本地发出的图片当然不需要去网络取了（也取不到哦），加载url传null即可
//                                , httpURL
//                                , imgLocalSavedName
//                                , asyncLoader);
//                    }
//                    else
                    {
                        loadThumbImageForShortVideo(context
                                , listAdapter
                                , (ImageView) viewHolder.mvContentIcon
                                // 本地没有缓存的话尝试从网络读取
                                , httpURL // @since 10.0：即使本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
                                , imgLocalSavedName
                                , asyncLoader);
                    }
                }
                break;
            }
            // 名片消息
            case MsgType.TYPE_CONTACT:
            {
                ContactMeta cm = ContactMeta.fromJSON(msgContent);
                if(cm != null)
                {
                    TextView viewNickName = (TextView)viewHolder.mvContent;
                    TextView viewDesc = viewHolder.mvContentDesc;
                    TextView viewType = viewHolder.ev_chatcontent_type_forContact;
                    ImageView viewHeadIcon = viewHolder.mvContentIcon;

                    String uid = cm.getUid();
                    String nickName = cm.getNickName();

                    if(CommonUtils.isStringEmpty(nickName))
                        nickName = "未定义";

                    // 显示昵称
                    viewNickName.setText(nickName);
                    // 显示额外信息
                    if(cm.getDesc() != null){
                        viewDesc.setText(cm.getDesc());
                    } else { // else是为了兼容老版本，因为老版本没有desc字段
                        viewDesc.setText((cm.getType() == ContactMeta.TYPE_USER ? "UID: " : "群ID: ") + (uid == null ? "未定义" : uid));
                    }
                    // 显示名片类型
                    viewType.setText(cm.getType() == ContactMeta.TYPE_USER?R.string.general_card_user_desc:R.string.general_card_group_desc);

                    // 载入头像图片
                    loadHeadIconForContact(context, cm.getType(), uid, viewHeadIcon, 0, -1);
                }
                break;
            }
            // 位置消息
            case MsgType.TYPE_LOCATION:
            {
                LocationMeta locationMeta = LocationMeta.fromJSON(msgContent);
                if(locationMeta != null)
                {
                    String locationTitle = locationMeta.getLocationTitle();
                    String locationContent = locationMeta.getLocationContent();
                    String prewviewImgFileName = locationMeta.getPrewviewImgFileName();

                    // 经度
                    double longitude = locationMeta.getLongitude();
                    // 纬度
                    double latitude = locationMeta.getLatitude();

                    // 显示的文字内容
                    ((TextView)viewHolder.mvContent).setText(CommonUtils.isStringEmpty(locationTitle, true)?context.getResources().getString(R.string.general_location_desc):locationTitle);
                    ((TextView)viewHolder.mvContentDesc).setText(CommonUtils.isStringEmpty(locationContent, true)?("经度:"+longitude+" 纬度:"+latitude):locationContent);

//					if(!CommonUtils.isStringEmpty(prewviewImgFileName, true))
                    {
                        String httpURL = null;
                        // 预览图文件名为空这种情况主要是Web产品发过来的消息，因为web不像app产品可以本地截预览图
                        if(CommonUtils.isStringEmpty(prewviewImgFileName)) {
                            httpURL = LocationUtils.getPreviewImageDownloadURL2(context, longitude, latitude);
                        }
                        else {
                            httpURL = LocationUtils.getPreviewImageDownloadURL(context, prewviewImgFileName, false);
                        }

                        Log.d(TAG, "[AAAAAAAAAAAA]httpURL="+httpURL);

                        if(isOutgoing){
                            // 发出的消息
                            loadPreviewImgForLocation(context
                                    , listAdapter
                                    , (ImageView) viewHolder.mvContentIcon
                                    // 本地发出的图片当然不需要去网络取了（也取不到哦），加载url传null即可
                                    , httpURL // @since 10.0：本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
                                    , prewviewImgFileName
                                    , asyncLoader);
                        }
                        // 收到的消息
                        else {
//                            loadPreviewImgForLocation(context
//									, listAdapter
//									, (ImageView) viewHolder.ev_chatcontent_previewimg_forLocation
//									// 本地没有缓存的话尝试从网络读取
//									, LocationUtils.getPreviewImageDownloadURL(context, prewviewImgFileName, false)
////									, LocationUtils.getPreviewImageDownloadURL(context, longitude, latitude)
//									, prewviewImgFileName
//									, asyncLoader);

                            // ## 加载远程预览图说明：
                            // 【问题】：由于高德地图的位置预览截图功能是由高德地图库内部完成，且是完全异步——不受开发者的程序控制，
                            //          那么存在一个问题：即当位置消息指令被收到时，而此截图很可能还没被上传到服务端，此时接收者
                            //          的聊天界面上就看不到这张图了（因为还没上传完就收取到了消息）。
                            // 【解决方法】：
                            //  方法1[不可行]：用AsyncBitmapLoader的话，因没有错误重试机制，所以肯定是不能加载的，那就想到了调用高德地图的静态图服务，
                            //               但由于AsyncBitmapLoader网络加载代码的实现不够通用，经过测试，无法加载高德地图的静态图服务（加载不到图）
                            //               ，也懒得再深究折腾了。
                            //  方法2[可行]：由于Picasso有错误重试功能（实测确实如此），所以即使第一次没有加载到，它也会在很短的时间内再次加载，所以也就不需要
                            //              调用高德地图的静态图服务了。（补充：有趣的是，Picasso可以加载高德地图的静态图服务，如果后面测试中遇到加载自已截的
                            //              预览图还是有加载问题的话，那就切换成加载高德地图的静态图服务吧，只是这又多了一个服务依赖，虽然方便，但能不用还是不用了！）

//                            AsyncBitmapLoader2.loadBitmap(viewHolder.ev_chatcontent_previewimg_forLocation
//                                    , httpURL
//                                    , R.drawable.chatting_location_preview_default
//                                    , R.drawable.chatting_location_preview_default);

                            Glide.with(context)
                                .load(httpURL)
                                .placeholder(R.drawable.chatting_location_preview_default)
                                .into(viewHolder.mvContentIcon);
                        }
                    }
                }
                break;
            }
            // 实时音视频聊天记录消息
            case MsgType.TYPE_VOIP$RECORD: {
                VoipRecordMeta vrm = VoipRecordMeta.fromJSON(msgContent);
                if (vrm != null) {
                    int iconResId = (vrm.getVoipType() == VoipRecordMeta.VOIP_TYPE_VOICE ?
                            R.drawable.chatting_voip_record_voice:R.drawable.chatting_voip_record_video);
                    String content = null;
                    switch (vrm.getRecordType()) {
                        case VoipRecordMeta.RECORD_TYPE_REQUEST_CANCEL:
                            content = (!isOutgoing?"对方":"")+context.getResources().getString(R.string.voip_record_cancel_common);
                            break;
                        case VoipRecordMeta.RECORD_TYPE_REQUEST_REJECT:
                            content = content = (!isOutgoing?"对方":"")+context.getResources().getString(R.string.voip_record_reject_common);
                            break;
                        case VoipRecordMeta.RECORD_TYPE_CALLING_TIMEOUT:
                            content = content = (isOutgoing?"对方"+context.getResources().getString(R.string.voip_record_timeout_common):""+context.getResources().getString(R.string.voip_record_timeout2_common));
                            break;
                        case VoipRecordMeta.RECORD_TYPE_CHATTING_DURATION: {
                            if (vrm.getDuration() > 0) {
                                content = MessageFormat.format(context.getResources().getString(R.string.voip_record_duration_common)
                                        , TimeToolKit.getMMSSFromSS(vrm.getDuration()));
                            }
                            break;
                        }
                    }

                    // 显示到ui上
                    viewHolder.mvContent.setText(content);
                    viewHolder.mvContentIcon.setImageDrawable(context.getResources().getDrawable(iconResId));
                }
                break;
            }
            // 所有未定义的消息都假设是文字，这样就不至于在不支持的消息时什么也不会显示了
            default:
            {
                // FFF emoji图片表情转换
                SpannableString ss = EmojiUtil.replaceEmoticons(context, msgContent, 50, 50, DynamicDrawableSpan.ALIGN_BOTTOM);
                viewHolder.mvContent.setText(ss);

                // 为聊天列表item的消息引用子ui设置显示内容
                setChatMessageQuoteItemContentValue(context, viewHolder, chatType, toId, quoteMeta);
                break;
            }
        }
    }

    /**
     * 为聊天列表item的消息引用子ui设置显示内容.
     *
     * @param context
     * @param viewHolder
     * @param msgType
     * @param quoteMeta 消息引用信息（当前仅用于文本消息时），此字段可为空（表示本条无引用消息）
     * @since 11.0
     */
    static void setChatMessageQuoteItemContentValue(Context context, ChattingListViewHolder viewHolder
            , int chatType, String toId, QuoteMeta quoteMeta) {

        try {
            // 如果有被引用的消息内容需要被显示
            if(quoteMeta != null && !CommonUtils.isStringEmpty(quoteMeta.getQuote_content())) {
                viewHolder.evQuoteLayout.setVisibility(View.VISIBLE);

                // 此状态表示被引用的原始消息已被撤回了
                if(quoteMeta.getQuote_status() == 1) {
                    String quoteContentForShow = context.getResources().getString(R.string.abstract_chat_message_list_view_quote_revoked);
                    viewHolder.evQuoteContentIconLayout.setVisibility(View.GONE);
                    viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_default);
                    viewHolder.mvContentDesc.setText(quoteContentForShow);
                    return;
                }

                // 消息内容的显示（比如图片消息会显示"[图片]"这样的字串）
                String quoteContentForShow = MessageExt.parseMessageContentPreview(context, quoteMeta.getQuote_content(), quoteMeta.getQuote_type());
                if(CommonUtils.isStringEmpty(quoteContentForShow)) {
                    quoteContentForShow = context.getResources().getString(R.string.abstract_chat_message_list_view_quote_unknow);
                }

                // 被引用消息发送者的昵称
                String quoteNick = Quote4InputWrapper.getQuoteNick(context, chatType, toId
                        , quoteMeta.getQuote_sender_uid(), quoteMeta.getQuote_sender_nick());
                quoteNick = (!CommonUtils.isStringEmpty(quoteNick) ? quoteNick +": " : "");
                String quoteNickToShow = quoteNick + quoteContentForShow;

                // 根据Glide官方指南的说明：https://muyangmin.github.io/glide-docs-cn/doc/getting-// 以下代码，解决Glide在RecycleView中图片错位或重复显示问题
                Glide.with(context).clear(viewHolder.mvContentIcon);// 解决方法2

                // 被引用消息的消息类型
                switch(quoteMeta.getQuote_type())
                {
                    case MsgType.TYPE_IMAGE:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_default);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.GONE);
                        viewHolder.mvContentDesc.setText(quoteNick);

                        String imageFileName = quoteMeta.getQuote_content();
                        if (imageFileName != null) {
                            String httpURL = SendImageHelper.getImageDownloadURL(context, "th_" + imageFileName, false);
                            String fname = "th_" + imageFileName ;// 收到的图片，只需要显示缩略图即可，缩略图在服务端的格式是：“th_文件名”

                            // loadImageForMessage()方法中载入的图片有可能不是正方形，所以它圆角后在此处的固定
                            // ImageView中就可能无法显示圆角了，所以这里换成Glide加载，Glide可以处理好图片的裁剪和
                            // 圆角问题，暂时就不要强求重用原图片消息的缓存了
                            ImageCacheLoader.loadImgWithGlide(context, httpURL, fname
                                    , viewHolder.mvContentIcon
                                    , 5
                                    , R.drawable.chatting_msg_item_quote_default
                                    , R.drawable.chatting_msg_item_quote_default
                                    , false, false);

//                            loadImageForMessage(context, listAdapter, viewHolder.mvContentIcon
//                                    , httpURL // @since 10.0：本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
//                                    , fname
//                                    , asyncLoader);
                        }
                        break;
                    }
                    // 语音留言消息
                    case MsgType.TYPE_VOICE:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_voice);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.GONE);
                        viewHolder.mvContentDesc.setText(quoteNickToShow);
                        break;
                    }
                    // “赠送的礼品”消息
                    case MsgType.TYPE_GIFT$SEND:
                    // “索取礼品”消息
                    case MsgType.TYPE_GIFT$GET:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.GONE);
                        viewHolder.mvContentDesc.setText(quoteNickToShow);
                        break;
                    }
                    // 文件消息
                    case MsgType.TYPE_FILE:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.GONE);

                        String fileName = context.getResources().getString(R.string.abstract_chat_message_list_view_quote_filebad);
                        FileMeta fileMeta = FileMeta.fromJSON(quoteMeta.getQuote_content());
                        if(fileMeta != null) {
                            fileName = fileMeta.getFileName();
                        }
                        // 显示文件信息
                        viewHolder.mvContentDesc.setText(quoteNickToShow);
                        viewHolder.mvContentIcon.setImageDrawable(context.getResources().getDrawable(SendFileHelper.getFileIconDrawableId(fileName)));

                        break;
                    }
                    // 短视频消息
                    case MsgType.TYPE_SHORTVIDEO:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_default);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.VISIBLE);
                        viewHolder.evQuoteContentTypeIcon.setImageResource(R.drawable.chatting_msg_item_quote_shortvideo_play_icon);
                        viewHolder.mvContentDesc.setText(quoteNick);

                        FileMeta fileMeta = FileMeta.fromJSON(quoteMeta.getQuote_content());
                        if(fileMeta != null)
                        {
                            String fileName = fileMeta.getFileName();
                            String fileMd5 = fileMeta.getFileMd5();

                            // 视频首帧预览图的文件名（本地保存的名）
                            final String imgLocalSavedName = ReceivedShortVideoHelper.constructShortVideoThumbName_localSaved(fileName);
                            String httpURL = ReceivedShortVideoHelper.getShortVideoThumbDownloadURL(context, imgLocalSavedName, fileMd5);
                            // loadThumbImageForShortVideo()方法中载入的图片不是正方形，所以它圆角后在此处的固定
                            // ImageView中就可能无法显示圆角了，所以这里换成Glide加载，Glide可以处理好图片的裁剪和
                            // 圆角问题，暂时就不要强求重用原短视频消息首帧预览图片的缓存了
                            ImageCacheLoader.loadImgWithGlide(context, httpURL, imgLocalSavedName
                                    , viewHolder.mvContentIcon
                                    , 5
                                    , R.drawable.chatting_msg_item_quote_default
                                    , R.drawable.chatting_msg_item_quote_default
                                    , false, false);
//                            loadThumbImageForShortVideo(context
//                                        , listAdapter
//                                        , (ImageView) viewHolder.mvContentIcon
//                                        // 本地没有缓存的话尝试从网络读取
//                                        , httpURL // @since 10.0：即使本地发出的消息，有可能来自收到的消息的转发，转发的当然就不存在本地缓存，所以需要加上这个url以备从网络加载
//                                        , imgLocalSavedName
//                                        , asyncLoader);
                        }
                        break;
                    }
                    // 名片消息
                    case MsgType.TYPE_CONTACT:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_default);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.GONE);
                        viewHolder.mvContentDesc.setText(quoteNickToShow+" 未知用户");

                        ContactMeta cm = ContactMeta.fromJSON(quoteMeta.getQuote_content());
                        if(cm != null) {
                            String uid = cm.getUid();
                            String nickName = cm.getNickName();
                            if(!CommonUtils.isStringEmpty(nickName)) {
                                // 显示昵称
                                viewHolder.mvContentDesc.setText(quoteNickToShow+" "+nickName);
                            }

                            // 载入头像图片
                            loadHeadIconForContact(context, cm.getType(), uid, viewHolder.mvContentIcon, 5, R.drawable.chatting_msg_item_quote_default);
                        }
                        break;
                    }
                    // 位置消息
                    case MsgType.TYPE_LOCATION:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.VISIBLE);
                        viewHolder.evQuoteContentTypeIcon.setVisibility(View.GONE);
                        viewHolder.mvContentIcon.setImageResource(R.drawable.chatting_msg_item_quote_location);
                        viewHolder.mvContentDesc.setText(quoteNickToShow);
                        break;
                    }
                    // 实时音视频聊天记录消息
                    case MsgType.TYPE_VOIP$RECORD: {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.GONE);
                        viewHolder.mvContentDesc.setText(context.getResources().getString(R.string.abstract_chat_message_list_view_quote_unsupported));
                        Log.w(TAG, "不支持实时音视频聊天记录消息的引用和显示！");
                        break;
                    }
                    // 所有未定义的消息都假设是文字，这样就不至于在不支持的消息时什么也不会显示了
                    default:
                    {
                        viewHolder.evQuoteContentIconLayout.setVisibility(View.GONE);
                        // FFF emoji图片表情转换
                        SpannableString ss = EmojiUtil.replaceEmoticons(context, quoteNickToShow, 50, 50, DynamicDrawableSpan.ALIGN_BOTTOM);
                        viewHolder.mvContentDesc.setText(ss);
                        break;
                    }
                }
            } else {
                viewHolder.evQuoteLayout.setVisibility(View.GONE);
                viewHolder.mvContentDesc.setText(null);
            }
        } catch (Exception e) {
            viewHolder.evQuoteLayout.setVisibility(View.GONE);
            viewHolder.mvContentDesc.setText(null);
            Log.w(TAG, e);
        }
    }

    /**
     * 为图片消息载入预览图的实用方法。
     * <p>
     * 如果是收到的消息将自动从网络加载它的缩略图而非全图，如果是发出的消息
     * 由直接从本地缓存加载（发出的消息已默认缓存到本地，无需从网络加载，节
     * 省网络流量）.
     *
     * @param context
     * @param listAdapter
     * @param viewAdavar
     * @param imageUrl
     * @param imageFileNameForLocal
     * @param asyncLoader
     */
    private static void loadImageForMessage(Context context, final BaseAdapter listAdapter, ImageView viewAdavar
            , String imageUrl, String imageFileNameForLocal, AsyncBitmapLoader asyncLoader)
    {
        // 根据图片URL去查找内存缓存有没有对应的Bitmap对象，并传递回调方法，如果没有，则等下载完毕回调
        Bitmap bitmap = asyncLoader.loadBitmap(viewAdavar
                , imageUrl
                , imageFileNameForLocal
                , new AsyncBitmapLoader.ImageCallBack()
                {
                    @Override
                    public void imageLoad(ImageView imageView, Bitmap bm)
                    {
//						Log.w(GoodDetailActivity.class.getSimpleName(), "【MALL】See here!!!!!!!!!!!!!!!!!!!!!"+bitmap.getWidth());

                        // 图片进行圆角处理并显示 @since7.1
                        toRound4ImageMessage(context, bm, imageView);

                        // ## 非常奇怪的一个问题：当网络下载的图片完成时会回调至此，但图片数据明
                        // ## 明有了却不能刷新显示之，目前为了它能显示就低效地notifyDataSetChanged
                        // ## 一下吧，以后看看什么方法可以单独刷新（否则每一次都得刷新所有可见区），
                        // ## 有可能是android的listview机制性问题
                        listAdapter.notifyDataSetChanged();
                    }

                    @Override
                    public void imageLoadFaild(ImageView imageView)
                    {
                        // 图片进行圆角处理并显示 @since7.1
                        toRound4ImageMessage(context, R.drawable.chatting_send_pic_faild, imageView);
                    }
                }
                // 指定生成的缩略图Bitmap对象所描述的图片大小(更小的尺寸将成倍地减小内存消耗)
                // 补充说明：服务端为了性能统一生成的是200 *200的缩略图，UI上只需要100dp*100dp就可以了
                // （此大小参考：chatting_list_item_right_image.xml中的@+id/tv_chatcontent，单位是像素）
                // 20190605 ：为了更好的适应高dpi手机，已由原200*200改为450*450
                , 450
                , 450
        );

        if(bitmap == null) {
            // 图片进行圆角处理并显示 @since7.1
            toRound4ImageMessage(context, R.drawable.chatting_send_pic_defalt, viewAdavar);
        }
        else {
            // 图片进行圆角处理并显示 @since7.1
            toRound4ImageMessage(context, bitmap, viewAdavar);
        }
    }

    /**
     * 为短视频消息载入首帧预览图的实用方法。
     *
     * @param context
     * @param listAdapter
     * @param view
     * @param imageUrl
     * @param imageFileNameForLocal
     * @param asyncLoader
     */
    private static void loadThumbImageForShortVideo(Context context, final BaseAdapter listAdapter, ImageView view
            , String imageUrl, String imageFileNameForLocal, AsyncBitmapLoader asyncLoader)
    {
        loadImageForCommon(context, listAdapter, view, imageUrl, imageFileNameForLocal, asyncLoader
                , R.drawable.common_default_short_video_thumb_120dp_3x
                , R.drawable.common_default_short_video_thumb_120dp_3x//R.drawable.common_default_img_no_border_fail_120dp_3x
                , 14 , 1, R.color.white);
    }

    /**
     * 为位置消息载入位置预览图的实用方法。
     *
     * @param context
     * @param listAdapter
     * @param view
     * @param imageUrl
     * @param imageFileNameForLocal
     * @param asyncLoader
     */
    private static void loadPreviewImgForLocation(Context context, final BaseAdapter listAdapter, ImageView view
            , String imageUrl, String imageFileNameForLocal, AsyncBitmapLoader asyncLoader)
    {
        loadImageForCommon(context, listAdapter, view, imageUrl, imageFileNameForLocal, asyncLoader
                , R.drawable.chatting_location_preview_default
                , R.drawable.chatting_location_preview_default
                // Bug FIX 20210927：不需要圆角处理，而且一旦处理会出现黑边效果
                , -1 , -1, -1);
    }

    /**
     * 一个通用的截入图片的实用方法。
     *
     * @param context
     * @param listAdapter
     * @param view
     * @param imageUrl
     * @param imageFileNameForLocal
     * @param asyncLoader
     */
    private static void loadImageForCommon(Context context
            , final BaseAdapter listAdapter, ImageView view
            , String imageUrl, String imageFileNameForLocal
            , AsyncBitmapLoader asyncLoader, int imgForDetault, final int imgForFial
            , int cornerRadius, int borderWidth, int borderColor)
    {
        // 根据图片URL去查找内存缓存有没有对应的Bitmap对象，并传递回调方法，如果没有，则等下载完毕回调
        Bitmap bitmap = asyncLoader.loadBitmap(view
                , imageUrl
                , imageFileNameForLocal
                , new AsyncBitmapLoader.ImageCallBack()
                {
                    @Override
                    public void imageLoad(ImageView imageView, Bitmap bitmap)
                    {
                        // 图片圆角处理 @since7.1
                        if(cornerRadius > 0){
                            RoundedBitmapDrawableUtility.toRound(context, bitmap, imageView, WidgetUtils.dip2px(context, cornerRadius), borderWidth, borderColor);
                        }
                        else
                            imageView.setImageBitmap(bitmap);

                        listAdapter.notifyDataSetChanged();
                    }

                    @Override
                    public void imageLoadFaild(ImageView imageView)
                    {
                        if(imgForFial != -1) {
                            // 图片圆角处理 @since7.1
                            if(cornerRadius > 0){
//                                Bitmap bm = BitmapHelper.drawableToBitmap(context, imgForFial);
//                                if(bm != null)
                                    RoundedBitmapDrawableUtility.toRound(context, imgForFial, imageView, WidgetUtils.dip2px(context, cornerRadius), borderWidth, borderColor);
                            }
                            else
                                imageView.setImageResource(imgForFial);
                        }
                    }
                }
        );

        if(bitmap == null) {
            if(imgForDetault != -1) {
                // 图片圆角处理 @since7.1
                if(cornerRadius > 0){
//                    Bitmap bm = BitmapHelper.drawableToBitmap(context, imgForFial);
//                    if(bm != null)
                        RoundedBitmapDrawableUtility.toRound(context, imgForFial, view, WidgetUtils.dip2px(context, cornerRadius), borderWidth, borderColor);
                }
                else
                    view.setImageResource(imgForDetault);
            }
        }
        else {
            // 图片圆角处理 @since7.1
            if(cornerRadius > 0){
                RoundedBitmapDrawableUtility.toRound(context, bitmap, view, WidgetUtils.dip2px(context, cornerRadius), borderWidth, borderColor);
            }
            else
                view.setImageBitmap(bitmap);
        }
    }

    /**
     * 为个人名片消息载入头像的实用方法。
     *
     * @param context
     * @param id
     * @param viewHeadIcon
     * @param roundCorner roundCorner 圆角半径，单位：dp，不需要圆角则请填0
     */
    private static void loadHeadIconForContact(Context context, int contactType, String id, ImageView viewHeadIcon, int roundCorner, int defaultResId)
    {
        if(viewHeadIcon != null){
            // 根据Glide官方指南的说明：https://muyangmin.github.io/glide-docs-cn/doc/getting-// 以下代码，解决Glide在RecycleView中图片错位或重复显示问题
            Glide.with(context).clear(viewHeadIcon);// 解决方法2

            // 如果是"个人名片"消息
            if(contactType == ContactMeta.TYPE_USER) {
                int defaultIconRes = (defaultResId == -1 ? R.drawable.main_alarms_chat_message_icon : defaultResId);
                // clear完成后设置默认占位图，确保图片显示不出现Glide导致的错位问题
                viewHeadIcon.setImageResource(defaultIconRes);
                if (id != null) {
                    // 加载头像图片
                    ImageCacheLoader.loadAvatarImgWithGlide(context, id, null
                            , viewHeadIcon
                            , roundCorner // 圆角单位是dp
                            , defaultIconRes
                            , true
                            , false);
                }
            }
            // 群名片消息
            else {
                int defaultIconRes = (defaultResId == -1 ? R.drawable.groupchat_groups_icon_default : defaultResId);
                // clear完成后设置默认占位图，确保图片显示不出现Glide导致的错位问题
                viewHeadIcon.setImageResource(defaultIconRes);
                if (id != null) {
                    // 加载群头像
                    ImageCacheLoader.loadGroupImgWithGlide(Glide.with(context), id
                            , viewHeadIcon
                            , roundCorner
                            , false
                            , defaultIconRes);// 圆角单位是dp，（相当于xxhdpi下的21像素）
                }
            }
        }
    }
    // **************************************************************************** 实用方法 END


    /**
     * 图片消息中的图片圆角化。
     *
     * @param c context
     * @param resId 原始图片资源id
     * @param toIV 要显示的目标控件
     */
    private static void toRound4ImageMessage(Context c, int resId, ImageView toIV)
    {
        Bitmap bm = BitmapHelper.drawableToBitmap(c, resId);
        if(bm != null)
            toRound4ImageMessage(c, bm, toIV);
    }

    /**
     * 图片消息中的图片圆角化。
     *
     * @param c context
     * @param originalBm 原始图片
     * @param toIV 要显示的目标控件
     */
    private static void toRound4ImageMessage(Context c, Bitmap originalBm, ImageView toIV)
    {
        RoundedBitmapDrawableUtility.toRound(c, originalBm, toIV, WidgetUtils.dip2px(c, 14), 1, R.color.white);
    }
}
